﻿/**
 * @file audio_dev.cpp
 * Copyright (c) Gaaagaa. All rights reserved.
 * 
 * @author  : Gaaagaa
 * @date    : 2020-02-13
 * @version : 1.0.0.0
 * @brief   : 封装 SDL 相关 API 实现音频 采集/播放 操作的工具类。
 */

#include "audio_dev.h"

#include <SDL/SDL_audio.h>
#include <string.h>

#ifdef _MSC_VER
#pragma warning(disable: 4302 4311 4312)
#endif // _MSC_VER

////////////////////////////////////////////////////////////////////////////////

/** 无效的音频设备对象句柄 */
#define X_INVALID_AUDIO_DEVICE   ((x_handle_t)(-1))

#define X_ASPEC_SAMPLES     4096

#define X_HANDLE(adevid)    (reinterpret_cast< x_handle_t >(adevid))
#define X_ADEVID(handle)    (reinterpret_cast< SDL_AudioDeviceID >(handle))

////////////////////////////////////////////////////////////////////////////////
// audio_dev_t

//====================================================================

// 
// audio_dev_t : common interfaces
// 

/**********************************************************/
/**
 * @brief 获取当前系统中的音频设备数量。
 * 
 * @param [in ] xut_mode : 0，播放设备；1，采集设备。
 * 
 * @return x_int32_t
 *         - 返回指定工作模式的设备数量。
 */
static inline x_int32_t adev_count(x_uint8_t xut_mode)
{
    return SDL_GetNumAudioDevices(xut_mode);
}

/**********************************************************/
/**
 * @brief 获取音频设备的名称（UTF-8编码的字符串）。
 * 
 * @param [in ] xit_index : 设备索引号（从 0 开始）。
 * @param [in ] xut_mode  : 0，播放设备；1，采集设备。
 * 
 * @return x_cstring_t
 *         - 返回指定的音频设备名称（UTF-8编码的字符串）。
 */
static inline x_cstring_t adev_name(x_int32_t xit_index, x_uint8_t xut_mode)
{
    return SDL_GetAudioDeviceName(xit_index, xut_mode);
}

/**********************************************************/
/**
 * @brief 以混合操作的方式，拷贝指定格式的音频数据。
 * 
 * @param [in ] xbt_dstbuf : 混合操作的目标音频数据缓存。
 * @param [in ] xbt_srcbuf : 混合操作的源音频数据缓存。
 * @param [in ] xut_format : 混合操作使用的音频格式（参看 xem_audio_format_t 枚举值）。
 * @param [in ] xut_length : 所操作的缓存有效数据长度。
 * @param [in ] xit_volume : 混合操作过程中，所限制的源音频音量值（取值范围 0 - 128）。
 * 
 */
x_void_t audio_dev_t::mix_copy(
                    x_byte_t * xbt_dstbuf,
                    x_byte_t * xbt_srcbuf,
                    x_uint16_t xut_format,
                    x_uint32_t xut_length,
                    x_int32_t  xit_volume /* = 128*/)
{
    SDL_MixAudioFormat(
        xbt_dstbuf,
        xbt_srcbuf,
        xut_format,
        xut_length,
        xit_volume);
}

//====================================================================

// 
// audio_dev_t : constructor/destructor
// 

/**
 * @param [in ] xut_wmode : 工作模式：0，播放模式；1，采集模式。
 */
audio_dev_t::audio_dev_t(x_uint8_t xut_wmode)
    : m_xht_device(X_INVALID_AUDIO_DEVICE)
{
    memset(&m_audio_spec, 0, sizeof(audio_spec_t));
    m_audio_spec.xut_wmode = xut_wmode;
}

audio_dev_t::~audio_dev_t(void)
{
    close();
}

//====================================================================

// 
// audio_dev_t : public interfaces
// 

/**********************************************************/
/**
 * @brief 设置音频设备的数据操作时的回调接口。
 * @note  该接口必须在 open() 操作前调用，即处于非打开状态下调用。
 * 
 * @param [in ] xft_callback : 回调的函数接口。
 * @param [in ] xht_userctxt : 回调用户自定义的上下文标识句柄。
 * 
 * @return x_bool_t
 *         - 返回 X_TRUE ，设置成功；
 *         - 返回 X_FALSE，对象处于非打开状态，设置失败。
 */
x_bool_t audio_dev_t::set_callback(
                            xfun_cbk_t xft_callback,
                            x_handle_t xht_userctxt)
{
    if (is_open())
    {
        return X_FALSE;
    }

    m_audio_spec.xft_callback = xft_callback;
    m_audio_spec.xht_userctxt = xht_userctxt;

    return X_TRUE;
}

/**********************************************************/
/**
 * @brief 打开音频设备。
 * 
 * @param [in ] xit_index     : 设备索引号（从 0 开始）；为 -1 时，取系统默认设备。
 * @param [in ] xut_channels  : 期望设备工作时的通道数。
 * @param [in ] xut_format    : 期望设备工作时的音频格式（参看 xem_audio_format_t 枚举值）。
 * @param [in ] xut_frequency : 期望设备工作时的采样率。
 * 
 * @return x_int32_t
 *         - 成功，返回 0 ；
 *         - 失败，返回 -1。
 */
x_int32_t audio_dev_t::open(x_int32_t  xit_index,
                               x_uint8_t  xut_channels,
                               x_uint16_t xut_format,
                               x_uint32_t xut_frequency)
{
    x_int32_t xit_err = -1;

    SDL_AudioSpec     xspec_want;
    SDL_AudioSpec     xspec_have;
    SDL_AudioDeviceID xid_audio = 0;

    x_cstring_t xszt_audio_dev =
        (-1 == xit_index) ? X_NULL : adev_name(xit_index, m_audio_spec.xut_wmode);

    memset(&xspec_want, 0, sizeof(SDL_AudioSpec));
    xspec_want.freq     = (x_int32_t)xut_frequency;
    xspec_want.format   = xut_format;
    xspec_want.channels = (x_uint8_t)xut_channels;
    xspec_want.samples  = X_ASPEC_SAMPLES;
    xspec_want.callback = (SDL_AudioCallback)m_audio_spec.xft_callback;
    xspec_want.userdata = (x_void_t *)m_audio_spec.xht_userctxt;

    xid_audio = SDL_OpenAudioDevice(
                            xszt_audio_dev,
                            m_audio_spec.xut_wmode,
                            &xspec_want,
                            &xspec_have,
                            0);
    if (0 == xid_audio)
    {
        close();
        xit_err = -1;
    }
    else
    {
        m_xht_device = X_HANDLE(xid_audio);

        m_audio_spec.xut_channels  = xspec_have.channels;
        m_audio_spec.xut_format    = xspec_have.format;
        m_audio_spec.xut_frequency = xspec_have.freq;

        xit_err = 0;

        pause(X_FALSE);
    }

    return xit_err;
}

/**********************************************************/
/**
 * @brief 关闭音频设备。
 */
x_void_t audio_dev_t::close(void)
{
    if (X_INVALID_AUDIO_DEVICE != m_xht_device)
    {
        SDL_CloseAudioDevice(X_ADEVID(m_xht_device));
        m_xht_device = X_INVALID_AUDIO_DEVICE;
    }

    m_audio_spec.xut_channels  = 0;
    m_audio_spec.xut_format    = 0;
    m_audio_spec.xut_frequency = 0;
    m_audio_spec.xft_callback  = X_NULL;
    m_audio_spec.xht_userctxt  = X_NULL;
}

/**********************************************************/
/**
 * @brief 判断当前音频设备是否已经处于打开的工作状态。
 */
x_bool_t audio_dev_t::is_open(void)
{
    return (X_INVALID_AUDIO_DEVICE != m_xht_device);
}

/**********************************************************/
/**
 * @brief 使当前打开的音频设备 暂停/恢复 工作。
 */
x_void_t audio_dev_t::pause(x_bool_t xbt_pause)
{
    if (is_open())
    {
        SDL_PauseAudioDevice(X_ADEVID(m_xht_device),
                             (x_int32_t)xbt_pause);
    }
}

/**********************************************************/
/**
 * @brief 返回当前音频设备中已缓存的数据大小。
 * @note  使用回调模式时，调用该接口无效。
 */
x_uint32_t audio_dev_t::buffer_size(void)
{
    if (is_open())
    {
        return SDL_GetQueuedAudioSize(X_ADEVID(m_xht_device));
    }

    return 0;
}

/**********************************************************/
/**
 * @brief 清除音频设备已缓存的数据。
 * @note  使用回调模式时，调用该接口无效。
 */
x_void_t audio_dev_t::clear_buffer(void)
{
    if (is_open())
    {
        SDL_ClearQueuedAudio(X_ADEVID(m_xht_device));
    }
}

////////////////////////////////////////////////////////////////////////////////
// adev_capture_t

//====================================================================

// 
// adev_capture_t : common interfaces
// 

/**********************************************************/
/**
 * @brief 获取音频采集设备的数量。
 */
x_int32_t adev_capture_t::dev_count(void)
{
    return adev_count(1);
}

/**********************************************************/
/**
 * @brief 获取音频采集设备的名称（UTF-8编码的字符串）。
 */
x_cstring_t adev_capture_t::dev_name(x_int32_t xit_index)
{
    return adev_name(xit_index, 1);
}

//====================================================================

// 
// adev_capture_t : constructor/destructor
// 

adev_capture_t::adev_capture_t(void)
    : audio_dev_t(1)
{

}

adev_capture_t::~adev_capture_t(void)
{

}

//====================================================================

// 
// adev_capture_t : public interfaces
// 

/**********************************************************/
/**
 * @brief 音频采集操作时，调用该接口采集实时的音频数据。
 * @note  在使用 非回调方式 采集音频数据时，调用该接口才有效。
 * 
 * @param [out] xmt_data : 接收采集到的音频数据缓存。
 * @param [in ] xut_size : 音频数据缓存的大小。
 * 
 * @return x_int32_t
 *         - 返回 >= 0，表示采集到的音频有效数据字节数；
 *         - 返回   -1，表示操作产生错误。
 */
x_int32_t adev_capture_t::capture(
                            x_byte_t * xmt_data,
                            x_uint32_t xut_size)
{
    if (!is_open() || is_callback())
    {
        return -1;
    }

    return (x_int32_t)SDL_DequeueAudio(
                        X_ADEVID(m_xht_device),
                        xmt_data,
                        xut_size);
}

////////////////////////////////////////////////////////////////////////////////
// adev_playout_t

//====================================================================

// 
// adev_playout_t : common interfaces
// 

/**********************************************************/
/**
 * @brief 获取音频播放设备的数量。
 */
x_int32_t adev_playout_t::dev_count(void)
{
    return adev_count(0);
}

/**********************************************************/
/**
 * @brief 获取音频播放设备的名称（UTF-8编码的字符串）。
 */
x_cstring_t adev_playout_t::dev_name(x_int32_t xit_index)
{
    return adev_name(xit_index, 0);
}

//====================================================================

// 
// adev_playout_t : constructor/destructor
// 

adev_playout_t::adev_playout_t(void)
    : audio_dev_t(0)
{

}

adev_playout_t::~adev_playout_t(void)
{

}

//====================================================================

// 
// adev_playout_t : public interfaces
// 

/**********************************************************/
/**
 * @brief 音频播放操作时，调用该接口播放实时的音频数据。
 * @note  在使用 非回调方式 播放音频数据时，调用该接口才有效。
 * 
 * @param [in ] xmt_data : 待播放的音频数据缓存。
 * @param [in ] xut_size : 音频数据缓存的有效数据大小。
 * 
 * @return x_int32_t
 *         - 返回  0，表示操作成功；
 *         - 返回 -1，表示操作失败。
 */
x_int32_t adev_playout_t::playout(
                            x_byte_t * xmt_data,
                            x_uint32_t xut_size)
{
    if (!is_open() || is_callback())
    {
        return -1;
    }

    return SDL_QueueAudio(
                X_ADEVID(m_xht_device),
                xmt_data,
                xut_size);
}
